/*
 * PKGBUILD parsing routines.
 */

/*-
 * Copyright (c) 2008 Paulo Matias
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 */

#include <boost/spirit/core.hpp>
#include <boost/spirit/utility/functor_parser.hpp>
#include <boost/spirit/utility/confix.hpp>
#include <boost/spirit/attribute.hpp>
#include <boost/spirit/phoenix/functions.hpp>

#include <boost/variant.hpp>

#include <iostream>
#include <fstream>
#include <string>
#include <vector>

#include <tr1/unordered_map>

#include <cstdio>
#include <cstdlib>

#include "parser.hpp"

using namespace std;
using namespace boost::spirit;
using namespace phoenix;

/*
 * The shell_string_parser class implements a functor parser for shell
 * script strings. The parser result, that is passed to semantic actions
 * attached to the parser, is a std::string. The result string can be
 * enclosed inside double quotes so it has the same meaning as the
 * original string. This behaviour is meant to uniformize the string
 * syntax - as the actual string value could be obtained by doing the
 * following replacements:
 *
 *      \\             =>      \
 *      \"             =>      "
 *      $var           =>      content of $var
 *      \$var          =>      $var
 * 
 * Examples of input values and its result strings are shown below:
 * 
 *      a\ b           =>      a b
 *      a\\\ b         =>      a\\ b
 *      "a b"c         =>      a bc
 *      '$a'"b c"      =>      \$ab c
 *      "\$a $b"       =>      \$a $b
 *      "\"\a"         =>      \"\\a
 *      '"$'           =>      \"\$
 *      '"\'           =>      \"\\
 * 
 */

struct shell_string_parser {
    typedef string result_t;
    template <typename ScannerT>
    ptrdiff_t operator()(ScannerT const& scan, result_t& result) const {
        
        // Our parser is a simple state machine with three states.
        
        enum state_t {
            STATE_OUTSIDE_QUOTES,
            STATE_INSIDE_DOUBLE_QUOTES,
            STATE_INSIDE_SINGLE_QUOTES
        };
        
        state_t state = STATE_OUTSIDE_QUOTES;
        result = "";
        std::ptrdiff_t len = 0;
        char a, b;

        while(!scan.at_end()) {
            a = *scan;
            
            switch(state) {

                case STATE_OUTSIDE_QUOTES:
                    switch(a) {
                        case ' ':
                        case '\t':
                        case '\n':
                        case ';':
                        case ')':
                            // end of string
                            goto shell_string_parser_stop;
                            
                        case '(':
                            // illegal char
                            return -1;
                
                        case '\\':
                            ++scan;
                            ++len;
                            b = *scan;
                            
                            switch(b) {
                                case '\n':
                                    // ignore "\\\n"
                                    break;
                                case '$':
                                case '\\':
                                    // keep escaping
                                    result += '\\';
                                default:
                                    result += b;
                            }
                            break;
                            
                        case '"':
                            state = STATE_INSIDE_DOUBLE_QUOTES;
                            break;
                            
                        case '\'':
                            state = STATE_INSIDE_SINGLE_QUOTES;
                            break;

                        default:
                            result += a;
                    }
                    break;

                case STATE_INSIDE_DOUBLE_QUOTES:
                    switch(a) {
                        case '\\':
                            ++scan;
                            ++len;
                            b = *scan;
                            
                            switch(b) {
                                case '\n':
                                    // ignore "\\\n"
                                    break;
                                case '"':
                                case '$':
                                case '\\':
                                    // keep escaping
                                    result += '\\';
                                    result += b;
                                    break;
                                default:
                                    // keep escaping
                                    result += "\\\\";
                                    result += b;
                            }
                            break;

                        case '"':
                            state = STATE_OUTSIDE_QUOTES;
                            break;

                        default:
                            result += a;
                    }
                    break;

                case STATE_INSIDE_SINGLE_QUOTES:
                    switch(a) {
                        case '"':
                        case '\\':
                        case '$':
                            // escape it
                            result += '\\';
                            result += a;
                            break;
                        case '\'':
                            state = STATE_OUTSIDE_QUOTES;
                            break;
                        default:
                            result += a;
                    }
                    break;
                    
            }
            
            ++scan;
            ++len;
        }

shell_string_parser_stop:;
        if(state != STATE_OUTSIDE_QUOTES) {
            // unclosed quotes
            return -1;
        }
        
        if(len == 0) {
            // don't accept an empty input
            return -1;
        }
    
        return len;
    }
};
// Construct a functor parser shell_string_parser_p.
functor_parser<shell_string_parser> shell_string_parser_p;
// Wrap shell_string_p inside a lexeme_d, so spaces are not skipped.
#define shell_string_p lexeme_d[shell_string_parser_p]

/*
 * push_back_shvar is a lambda function for adding stuff to a shvar_t
 * (boost::variant based) container.
 */
struct push_back_shvar_impl
{
    template <typename Container, typename Item>
    struct result
    {
        typedef void type;
    };

    template <typename Container, typename Item>
    void operator()(Container& c, Item const& item) const
    {
        // call boost::get to get content from the variant
        boost::get<shvar_vec_t&>(c).push_back(item);
    }
};
// construct the lambda function
function<push_back_shvar_impl> const push_back_shvar = push_back_shvar_impl();

/*
 * assign_key is a lambda function to do c[key] = val
 */
struct assign_key_impl
{
    template <typename Container, typename Key, typename Val>
    struct result
    {
        typedef void type;
    };

    template <typename Container, typename Key, typename Val>
    void operator()(Container& c, Key const& key, Val const& val) const
    {
        c[key] = val;
    }
};
// construct the lambda function
function<assign_key_impl> const assign_key = assign_key_impl();

/*
 * assig_closure is a closure that keeps the variable value and variable
 * name for the assignment beign currently parsed.
 */
struct assig_closure : boost::spirit::closure<assig_closure,
                                              shvar_t, string> {
    member1 var_val;
    member2 var_name;
};
// ctx_t is the context type for using an assig_closure.
typedef assig_closure::context_t ctx_t;

/*
 * pkgbuild_grammar is a grammar for parsing PKGBUILD files.
 * 
 * This grammar is somewhat simplified, but it passed the
 * entire ABS tree as of 2008-07-25.
 * 
 */
struct pkgbuild_grammar: public grammar<pkgbuild_grammar, ctx_t> {
    template <typename ScannerT>
    struct definition {
        definition(pkgbuild_grammar const& self) {
            first = 
            (
                shell_script = *(shell_cmd >> (eol_p | ';'))
                               >> lexeme_d[*skip] // ignore trailing spaces
                               >> !shell_cmd,     // ignore missing newline
            
                shell_cmd = shell_function | (
                                (+assignment) ||    // assignments
                                (*(anychar_p - eol_p - ';'))  // any cmd
                            ),

                assignment = lexeme_d[
                                   /* keep the variable name */
                        identifier[assignment.var_name = construct_<string>(arg1, arg2)]
                        >> '=' >> shell_value]
                             /* add variable to the map */
                            [assign_key(var(self.var_map), assignment.var_name,
                                construct_<shvar_info>(arg1, arg2, assignment.var_val)
                            )],

                shell_value = shell_array   /* is set in shell_array */ |
                              shell_string_p[assignment.var_val = arg1] |
                              eps_p         [assignment.var_val = ""],

                                        /* initializes the container */
                shell_array = ch_p('(')[assignment.var_val = construct_<shvar_vec_t>()]
                              >> *skip >> !(shell_string_p
                                     /* adds value to the container */
                                    [push_back_shvar(assignment.var_val, arg1)]
                              % (+skip)) >> *skip >> ')',

                identifier = lexeme_d[(alpha_p | '_') >>
                                     *(alnum_p | '_')],
                                     
                shell_function = identifier >> '(' >> ')' >> *eol_p >>
                        confix_p('{',*(shell_string_p |           // skip strings
                                       comment_nest_p('{','}') |  // skip ${var}
                                       anychar_p                  // skip another stuff
                                      ),'}'),

                skip = (space_p | comment_p('#') | ('\\' >> eol_p))
            );
            
        }
        
        subrule<0>          shell_script;
        subrule<1>          shell_cmd;
        subrule<2, ctx_t>   assignment;
        subrule<3>          shell_value;
        subrule<4>          shell_array;
        subrule<5>          identifier;
        subrule<6>          shell_function;
        subrule<7>          skip;
        
        rule<ScannerT> first;
        rule<ScannerT> const& start() const { return first; }
        
    };
    
    shvar_map &var_map;
    
    pkgbuild_grammar(shvar_map &var_map_) :var_map(var_map_) {}
};

void file_read(FILE *fp, string& s) {
    char buf[1024];
    while(size_t len = fread(buf, 1, sizeof(buf), fp)) {
        s += string(buf, len);
    }
}

int main(int argc, char **argv) {
    shvar_map var_map;
    pkgbuild_grammar g(var_map);
    string s;
    
    FILE *fp = fopen(argv[1], "r");
    file_read(fp, s);
    fclose(fp);
    
    cout << s << endl;

    if (parse(s.begin(), s.end(),
              g,
              (ch_p(' ') | '\t' | comment_p('#') | ('\\' >> eol_p))
             ).full)
        cout << "parsing succeeded\n";
    else
        cout << "parsing failed\n";
    
    
    cout << endl;
    for(shvar_map::iterator i = var_map.begin(); i != var_map.end(); ++i) {
        cout << "var name: \"" << i->first << "\"\n";
        shvar_info &info = i->second;
        if(shvar_str_t *vs = boost::get<shvar_str_t>(&info.value)) {
            cout << "var value is string: \"" << *vs << "\"\n";
        }
        else if(shvar_vec_t *vt = boost::get<shvar_vec_t>(&info.value)) {
            cout << "var value is array:\n";
            for(shvar_vec_t::iterator j = vt->begin(); j != vt->end(); ++j) {
                cout << "\t\"" << *j << "\"\n";
            }
        }
        cout << "original code: " << string(info.begin, info.end) << endl;
        cout << endl;
    }
    
    return 0;
}
